﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using ProtoBuf;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO.Compression;


namespace RapidTestMapMaker
{
    public static class MapSerializer
    {
        public static void Serialize(BigMap map, TileSet tileset, bool IncludeTileset, Stream destination, bool useCompression = true)
        {
            if (useCompression)
            {
                using (DeflateStream ds = new DeflateStream(destination, CompressionMode.Compress))
                { DoSerialize(map, tileset, IncludeTileset, ds); }
            }
            else
            { DoSerialize(map, tileset, IncludeTileset, destination); }
        }
        private static void DoSerialize(BigMap map, TileSet tileset, bool IncludeTileset, Stream destination)
        {           
            SerializedMap result = new SerializedMap();
            Dictionary<Tile, int> tileLookup = new Dictionary<Tile, int>();
            if (tileset != null)
            {
                result.TileHeight = tileset.TileHeight;
                result.TileWidth = tileset.TileWidth;

                foreach (Tile tile in tileset.Tiles)
                {
                    if (IncludeTileset)
                    {
                        result.Tiles.Add(SaveImage(tile.FullImage));
                    }
                    tileLookup.Add(tile, tileLookup.Count);
                }
            }
            else
            { result.Tiles = null; }
            foreach (var layer in map.Layers)
            {
                Rectangle r = layer.ImageBounds;
                MapChunk chunk = new MapChunk();
                chunk.X = r.Left;
                chunk.Y = r.Top; ;
                chunk.Width = r.Width;
                chunk.Height = r.Height;
                for (int x = r.Left; x < r.Right; x++)
                {
                    for (int y = r.Top; y < r.Bottom; y++)
                    {
                        Tile tile = layer[x, y];
                        int index = -1;
                        if (tile != null)
                        {
                            if (!tileLookup.TryGetValue(tile, out index))
                            {
                                index = tileLookup.Count;
                                tileLookup.Add(tile, index);
                            }
                        }
                        chunk.Data.Add(index);
                    }
                }
                MapLayer serializedLayer = new MapLayer();
                serializedLayer.Visible = layer.Visible;
                serializedLayer.IsWall = layer.IsWall;
                serializedLayer.Chunks.Add(chunk);
                result.Layers.Add(serializedLayer);
            }            
            Serializer.Serialize(destination, result);
        }

        public static BigMapAndTileSet Deserialize(Stream input, TileSet currentTileSet = null, bool isCompressed = true)
        {
            if (isCompressed)
            {
                using (DeflateStream ds = new DeflateStream(input, CompressionMode.Decompress))
                { return DoDeserialize(ds, currentTileSet); }
            }
            else
            { return DoDeserialize(input, currentTileSet); }
        }
        private static BigMapAndTileSet DoDeserialize(Stream input, TileSet currentTileSet)
        {
            SerializedMap serialized = Serializer.Deserialize<SerializedMap>(input);
            BigMapAndTileSet result = new BigMapAndTileSet();
            BigMap map = new BigMap();
            map.ClearLayers();
            result.Map = map;
            result.TileSet = new TileSet();
            List<Tile> tileList = new List<Tile>();
            if (serialized.Tiles != null && serialized.Tiles.Count > 0)
            {
                result.TileSet.TileWidth = serialized.TileWidth;
                result.TileSet.TileHeight = serialized.TileHeight;
                foreach (byte[] image in serialized.Tiles)
                {
                    Tile tile = new Tile();
                    tile.FullImage = LoadImage(image);
                    tileList.Add(tile);
                    result.TileSet.AddTile(tile);
                }
            }
            else if (currentTileSet != null)
            {
                result.TileSet = currentTileSet;
                foreach (Tile tile in currentTileSet.Tiles)
                { tileList.Add(tile); }
            }
            foreach (MapLayer layer in serialized.Layers)
            {
                RapidTestMapMaker.MapLayer newLayer = new RapidTestMapMaker.MapLayer();
                newLayer.IsWall = layer.IsWall;
                map.AddLayer(newLayer);
                foreach (MapChunk chunk in layer.Chunks)
                {
                    int l = chunk.X;
                    int t = chunk.Y;
                    int r = l + chunk.Width;
                    int b = t + chunk.Height;
                    int h = chunk.Height;
                    IList<int> data = chunk.Data;
                    for (int x = l; x < r; x++)
                    {
                        for (int y = t; y < b; y++)
                        {
                            int index = data[((x - l) * h) + (y - t)];
                            if (index != -1)
                            {
                                while (index >= tileList.Count)
                                {
                                    Tile missingTile = new Tile();
                                    tileList.Add(missingTile);
                                    result.TileSet.AddTile(missingTile);
                                }
                                newLayer[x, y] = tileList[index];
                            }
                        }
                    }
                }
            }
            return result;
        }

        private static byte[] SaveImage(Image image)
        {
            if (image == null) { return null; }
            using (MemoryStream ms = new MemoryStream())
            {
                image.Save(ms, ImageFormat.Png);
                ms.Flush();
                return ms.ToArray();
            }
        }

        private static Image LoadImage(byte[] image)
        {
            if (image == null) { return null; }
            //The following ugly hack is a workaround for a known GDI issue that
            // occurs sometimes.  It messes up the image flags and causes an OutOfMemory 
            // exception when attempting to draw the image.  It's not actual out of memory
            // it's just a really bad useless error message.  The solution is to clone the
            // image after loading and dispose the original.
            // http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/2b813a2e-321b-46ae-9446-47d5b5a82eea
            Image temp;
            using (MemoryStream ms = new MemoryStream(image))
            { temp = Image.FromStream(ms); }
            Image result = new Bitmap(temp);
            temp.Dispose();
            return result;
        }

        public class BigMapAndTileSet
        {
            public BigMap Map { get; set; }
            public TileSet TileSet { get; set; }
        }

        [ProtoContract]
        private class SerializedMap
        {
            [ProtoMember(1)]
            private IList<byte[]> tiles = new List<byte[]>();
            public IList<byte[]> Tiles { get { return tiles; } set { tiles = value; } }

            [ProtoMember(2)]
            private IList<MapLayer> layers = new List<MapLayer>();
            public IList<MapLayer> Layers { get { return layers; } }

            [ProtoMember(3)]
            public int TileWidth { get; set; }
            [ProtoMember(4)]
            public int TileHeight { get; set; }
        }

        [ProtoContract]
        private class MapLayer
        {
            [ProtoMember(1)]
            public String Name { get; set; }
            [ProtoMember(2)]
            public bool Visible { get; set; }

            [ProtoMember(3)]
            private IList<MapChunk> chunks = new List<MapChunk>();
            public IList<MapChunk> Chunks
            { get { return chunks; } }

            [ProtoMember(4)]
            public bool IsWall { get; set; }
        }

        [ProtoContract]
        private class MapChunk
        {
            [ProtoMember(1)]
            public int X { get; set; }
            [ProtoMember(2)]
            public int Y { get; set; }
            [ProtoMember(3)]
            public int Width { get; set; }
            [ProtoMember(4)]
            public int Height { get; set; }
            [ProtoMember(5)]
            private IList<int> data = new List<int>();
            public IList<int> Data { get { return data; } }
        }
    }
}
